#include "precomp.h"

namespace Tmpl {

#define MOUSE_BUTTON_COUNT	7

// ===========================================================
// Global input objects
// ===========================================================

Key key;
Mouse mouse;
Input input;

// ===========================================================
// Key class implementation
// ===========================================================

struct KeyData
{
	bool down[TK_KEY_COUNT];
	bool press[TK_KEY_COUNT];
	bool release[TK_KEY_COUNT];

	int downCounter;
	bool anyPress;
	bool anyRelease;

	std::vector<InputEvent> events;
};

Key::Key() : myData(*new KeyData)
{
	for(int i=0; i<TK_KEY_COUNT; ++i) myData.down[i] = myData.press[i] = myData.release[i] = false;
	myData.anyPress = myData.anyRelease = false;
	myData.downCounter = 0;
}

Key::~Key()
{
	delete (&myData);
}

bool Key::IsDown(unsigned int keycode) const
{
	if(keycode >= 'A' && keycode <= 'Z') keycode += 'a'-'A';
	switch(keycode)
	{
		case TK_ANYKEY:	return (myData.downCounter > 0);
		case TK_SHIFT:	return (myData.down[TK_SHIFT_L ] || myData.down[TK_SHIFT_R ]);
		case TK_CTRL:	return (myData.down[TK_CTRL_L  ] || myData.down[TK_CTRL_R  ]);
		case TK_ALT:	return (myData.down[TK_ALT_L   ] || myData.down[TK_ALT_R   ]);
		case TK_SYSTEM:	return (myData.down[TK_SYSTEM_L] || myData.down[TK_SYSTEM_R]);
		default:		return (myData.down[keycode]);
	}
}

bool Key::IsPressed(unsigned int keycode) const
{
	if(keycode >= 'A' && keycode <= 'Z') keycode += 'a'-'A';
	switch(keycode)
	{
		case TK_ANYKEY:	return myData.anyPress;
		case TK_SHIFT:	return (myData.press[TK_SHIFT_L ] || myData.press[TK_SHIFT_R ]);
		case TK_CTRL:	return (myData.press[TK_CTRL_L  ] || myData.press[TK_CTRL_R  ]);
		case TK_ALT:	return (myData.press[TK_ALT_L   ] || myData.press[TK_ALT_R   ]);
		case TK_SYSTEM:	return (myData.press[TK_SYSTEM_L] || myData.press[TK_SYSTEM_R]);
		default:		return (myData.press[keycode]);
	}
}

bool Key::IsReleased(unsigned int keycode) const
{
	if(keycode >= 'A' && keycode <= 'Z') keycode += 'a'-'A';
	switch(keycode)
	{
		case TK_ANYKEY:	return myData.anyRelease;
		case TK_SHIFT:	return (myData.release[TK_SHIFT_L ] || myData.release[TK_SHIFT_R ]);
		case TK_CTRL:	return (myData.release[TK_CTRL_L  ] || myData.release[TK_CTRL_R  ]);
		case TK_ALT:	return (myData.release[TK_ALT_L   ] || myData.release[TK_ALT_R   ]);
		case TK_SYSTEM:	return (myData.release[TK_SYSTEM_L] || myData.release[TK_SYSTEM_R]);
		default:		return (myData.release[keycode]);
	}
}

unsigned int Key::GetEventCount() const
{
	return myData.events.size();
}

const InputEvent& Key::GetEvent(unsigned int index) const
{
	return myData.events.at(index);
}

// ===========================================================
// MouseButton class implementation
// ===========================================================

struct MouseData
{
	bool down[MOUSE_BUTTON_COUNT];
	bool click[MOUSE_BUTTON_COUNT];
	bool release[MOUSE_BUTTON_COUNT];
	bool doubleClick[MOUSE_BUTTON_COUNT];

	int x, y;
	int prevX, prevY;
	int scrollDelta;

	std::vector<InputEvent> events;
};

Mouse::Mouse() : myData(*new MouseData)
{
	for(int i=0; i<MOUSE_BUTTON_COUNT; ++i)
	{
		myData.down[i] = myData.click[i] = myData.release[i] = myData.doubleClick[i] = false;
	}
	myData.x = myData.y = myData.prevX = myData.prevY = 0;
	myData.scrollDelta = 0;
}

Mouse::~Mouse() 
{
	delete (&myData);
}

bool Mouse::IsInRect(int x, int y, int w, int h) const
{
	return (myData.x >= x && myData.y >= y && myData.x <= x+w && myData.y <= y+h);
}

bool Mouse::IsDown(int button) const			{return myData.down[button];}
bool Mouse::IsClicked(int button) const			{return myData.click[button];}
bool Mouse::IsReleased(int button) const		{return myData.release[button];}
bool Mouse::IsDoubleClicked(int button) const	{return myData.doubleClick[button];}

int Mouse::GetX() const							{return myData.x;}
int Mouse::GetY() const							{return myData.y;}
int Mouse::GetXPrev() const						{return myData.prevX;}
int Mouse::GetYPrev() const						{return myData.prevY;}
int Mouse::GetScrollDelta() const				{return myData.scrollDelta;}

unsigned int Mouse::GetEventCount() const
{
	return (int)myData.events.size();
}

const InputEvent& Mouse::GetEvent(unsigned int index) const
{
	return myData.events.at(index);
}

// ===========================================================
// Imput class implementation
// ===========================================================

Input::Input() : myMouse(&mouse), myKeys(&key)
{
}

Input::~Input()
{
}

void Input::BindTo(Mouse* mouse, Key* keys)
{
	myMouse = mouse;
	myKeys = keys;
}

void Input::PressKey(unsigned int keycode)
{
	if(!myKeys->myData.down[keycode]) ++myKeys->myData.downCounter;
	
	myKeys->myData.down[keycode] = true;
	myKeys->myData.press[keycode] = true;
	myKeys->myData.anyPress = true;

	InputEvent evt = {InputEvent::KEY_PRESS, keycode, 0};
	myKeys->myData.events.push_back(evt);
}

void Input::ReleaseKey(unsigned int keycode)
{
	if(myKeys->myData.down[keycode]) --myKeys->myData.downCounter;
	
	myKeys->myData.down[keycode] = false;
	myKeys->myData.release[keycode] = true;
	myKeys->myData.anyRelease = true;

	InputEvent evt = {InputEvent::KEY_RELEASE, keycode, 0};
	myKeys->myData.events.push_back(evt);
}

void Input::ClickMb(unsigned int button)
{
	myMouse->myData.down[button] = true;
	myMouse->myData.click[button] = true;

	// Handle double clicking
	static double lastClickTime[MOUSE_BUTTON_COUNT] = {0.0};
	const double thisClickTime = WinGetSystemTime();
	if(thisClickTime - lastClickTime[button] < 0.25)
	{
		myMouse->myData.doubleClick[button] = true;
	}
	lastClickTime[button] = thisClickTime;

	// Push event message
	InputEvent evt = {InputEvent::MOUSE_CLICK, 0, button};
	myMouse->myData.events.push_back(evt);
}

void Input::ReleaseMb(unsigned int button)
{
	myMouse->myData.down[button] = false;
	myMouse->myData.release[button] = true;

	// Push event message
	InputEvent evt = {InputEvent::MOUSE_RELEASE, 0, button};
	myMouse->myData.events.push_back(evt);
}

void Input::SetMousePos(int x, int y)
{
	myMouse->myData.prevX = myMouse->myData.x;
	myMouse->myData.prevY = myMouse->myData.y;
	myMouse->myData.x = x;
	myMouse->myData.y = y;
}

void Input::ScrollMouseWheel(bool up)
{
	myMouse->myData.scrollDelta += up ? 1 : -1;
}

void Input::SetKeysHandled()
{
	myKeys->myData.anyPress = false;
	memset(&myKeys->myData.press[0],	0, sizeof(bool) * TK_KEY_COUNT);
	myKeys->myData.events.clear();
}

void Input::SetMouseHandled()
{
	myMouse->myData.scrollDelta = 0;
	memset(&myMouse->myData.click[0],       0, sizeof(bool) * MOUSE_BUTTON_COUNT);
	memset(&myMouse->myData.doubleClick[0], 0, sizeof(bool) * MOUSE_BUTTON_COUNT);
	myMouse->myData.events.clear();
}

void Input::Reset()
{
	SetKeysHandled();
	SetMouseHandled();

	memset(&myKeys->myData.release[0],  0, sizeof(bool) * TK_KEY_COUNT);
	memset(&myMouse->myData.release[0], 0, sizeof(bool) * MOUSE_BUTTON_COUNT);

	myMouse->myData.prevX = myMouse->myData.x;
	myMouse->myData.prevY = myMouse->myData.y;
}

}; // namespace Tmpl